﻿/**
 * LidarPoints.cs
 * creates the LiDAR points used in the visualization. 
 * decpreciates GenericPointSpritePartciles.cs
 * */



using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using System.Data;

namespace GenericExampleParticleSystem//to be changed later.
{
    public class LidarPoints : DrawableGameComponent
    {
        VertexDeclaration m_vDec;//the object that must be declared to set up the device and shader info on each vertex
        public int partCount;//the number of particles
        private LidarPoint[] m_sprites;//collection of lidar points
        string textureAsset;//string of texture
        Texture2D myTexture;//texture of the lidar point
        public Vector3 myPosition;
        public Vector3 myScale;
        public Quaternion myRotation;
        public Color pointColor;//the lidar point color
        public float myPointSize = 5f;//the size of the billboards
        string shaderAsset;//the shader string
        Effect effect;//the shader effect
        //device stuff
        ContentManager content;
        GraphicsDeviceManager myDeviceManager;
        GraphicsDevice myDevice;
        private VertexDeclaration positionColorVertexDeclaration;

        
        public struct LidarPoint
        {            
            public Vector3 position;//location of lidar point. X,Y,Z
            public Color color;//Lidar point color. Useful for intensity and multispectral.
            public Vector4 data; //for texture coordinate ?            
            

            //struct constructor
            public LidarPoint(Vector3 pos, Color col, Vector4 d)
            {
                position = pos;
                color = col;
                data = Vector4.Zero;
            }
            //declaring a VertexElement array
            public static readonly VertexElement [] vertexElements = new VertexElement[]
            {
                //an array of vertex elements to set up the vertexDeclaration. 
                //the sizeof(float)*3(7) is because of the cumulative offset
                new VertexElement(0,0, VertexElementFormat.Vector3, VertexElementMethod.Default,VertexElementUsage.Position,0),                           //position
                new VertexElement(0,sizeof(float)*3,VertexElementFormat.Color,VertexElementMethod.Default,VertexElementUsage.Color,0),                    //color
                new VertexElement(0,sizeof(float)*7,VertexElementFormat.Vector4,VertexElementMethod.Default,VertexElementUsage.TextureCoordinate,0),      //texture
            };

            //the size of 13 bytes.... why is this here?
            //helper method for operator....
            public static int SizeInBytes
            {
                get
                {
                    return sizeof(float) * 13;
                }
            }
            #region Operator overloads
            public static bool operator !=(LidarPoint left,LidarPoint right)
            {
                return left.GetHashCode() != right.GetHashCode();
            }
            public static bool operator == (LidarPoint left, LidarPoint right)
            {
                return left.GetHashCode() == right.GetHashCode();
            }
            public override bool Equals (object obj)
            {
                return this == (LidarPoint) obj;
            }
            public override int GetHashCode()
            {
                return position.GetHashCode() | color.GetHashCode() | data.GetHashCode();
            }
            public override string ToString()
            {
               return position.ToString();
            }
            #endregion
        }//end of struct

        //Getter/setter for the size of earch lidar point on screen
        public int ParticleSize
        {
            get { return m_sprites.Length; }
            set
            {
                myPointSize = 10;
                RefreshPoints();
            }
        }

        //LidarPoints class constructor
        public LidarPoints(Game game, GraphicsDeviceManager graphicsDeviceManager, string texture, string shader, int particleCount)
            : base(game)
        {
            //get game data
            content = new ContentManager(game.Services);
            myDeviceManager = graphicsDeviceManager;

            //set up some vars
            myPosition = Vector3.Zero;//at origin
            myScale = Vector3.One;
            myRotation = new Quaternion(0, 0, 0, 1);//at the origin with a angle

            //this doesn't compute. why is this in here? Note: try removing...
            Rotate(Vector3.Left, MathHelper.PiOver2);

            //fed in number of particles. should this be here?
            //i think this can be removed.
            //should just get the particle number from the ReadLidar Class
            partCount = particleCount;

            //don't think this actually matters... the color is overwritten by the texture color
            pointColor = Color.YellowGreen;

            //texture and shader info
            textureAsset = texture;
            shaderAsset = shader;
            RefreshPoints(); // why is refresh called? if not needed, must be removed.
        }//end of class constructor

        //creates a rotation in the myRotation Quaternion
        //in the Draw command, the myRotation changes the world view projection
        public void Rotate(Vector3 axis, float angle)
        {
            //Transforms a 3D vector by the given matrix
            //the matrx is created from where the camera is currently; myRotation
            axis = Vector3.Transform(axis, Matrix.CreateFromQuaternion(myRotation));
            
            //myRotation is updated to reflect the change. 
            //first makes a quaternion, then makes the vecitr of standard length
            //Quaternion.Normalize = Divides each component of the quaternion by the length of the quaternion.
            //Quaternion.CreateFromAxisAngle = Creates a Quaternion from a vector and an angle to rotate about the vector.
            myRotation = Quaternion.Normalize(Quaternion.CreateFromAxisAngle(axis, angle) * myRotation);
        }

        //changes the myPosition var
        //in the Draw command, the myPosition changes the world view projection
        public void Translate(Vector3 distance)
        {
            //pretty obvious, moves the position variable by a distance at the rotation
            myPosition += Vector3.Transform(distance, Matrix.CreateFromQuaternion(myRotation));
        }

        //revolve the data. two stage process.
        //first, figure out the rotation.
        //second, find out how far the camera has moved.
        public void Revolve(Vector3 target, Vector3 axis, float angle)
        {
            //update the myRotation variable
            Rotate(axis, angle);
            //create an axis that is in the world coords
            Vector3 revolveAxis = Vector3.Transform(axis, Matrix.CreateFromQuaternion(myRotation));
            //make a quaternion for rotation
            Quaternion rotate = Quaternion.CreateFromAxisAngle(revolveAxis, angle);
            //update the myPosition var for draw command.
            myPosition = Vector3.Transform(target - myPosition, Matrix.CreateFromQuaternion(rotate));
        }

        //clear the lidar points from memory
        protected override void UnloadContent()
        {
            content.Unload();
            base.UnloadContent();
        }


        // run when class is initiated. sets up textures, video card, shader ...
        protected override void LoadContent()
        {
            //set the render device
            myDevice = myDeviceManager.GraphicsDevice;
            //set up the correct texture
            myTexture = content.Load<Texture2D>(textureAsset);

            //create the lidar point elements
            m_vDec = new VertexDeclaration(myDevice, LidarPoint.vertexElements);
            //set the type of declarations
            positionColorVertexDeclaration = new VertexDeclaration(myDevice, VertexPositionColor.VertexElements);

            //set the shader up with the texture
            effect = content.Load<Effect>(shaderAsset);
            try
            {
                effect.Parameters["particleTexture"].SetValue(myTexture);
            }
            catch { }

            //read the lidar file
            RefreshPoints();

            //call to base to make sure that anything not covered here gets done
            base.LoadContent();
        }

        //the main event. loading of the lidar points from the file into the vis environment
        private void RefreshPoints()
        {
            //ReadLidar rl = new ReadLidar("../../../building10000pts.xyzi",'\t');
            //ReadLidar rl = new ReadLidar("../../../blueprints.xyzi",'\t');
            //ReadLidar rl = new ReadLidar("../../../cornerhouse.xyzi", ' ');
            //ReadLidar rl = new ReadLidar("../../../truck.xyzi",' ');
            ReadLidar rl = new ReadLidar("../../../house.xyzi", ' ');
            //ReadLidar rl = new ReadLidar("../../../courthouse.xyzi",' ');
            //ReadLidar rl = new ReadLidar("../../../planter.xyzi",' ');
            //ReadLidar rl = new ReadLidar("../../../LargeData.xyzi",' ');
            //ReadLidar rl = new ReadLidar("../../../smallBlock.xyzi",' ');
            //ReadLidar rl = new ReadLidar("../../../kingstfaces.xyzi",' ');//whig,700k points
            //ReadLidar rl = new ReadLidar("../../../Sensor1Strip001.xyzi",'\t');
            //ReadLidar rl = new ReadLidar("../../../streetfront.xyzi", ' '); //busy, trees,fairly large amount of face
            //ReadLidar rl = new ReadLidar("../../../streetfront2.xyzi", ' ');// good road section
            //ReadLidar rl = new ReadLidar("../../../streetfront3.xyzi", ' ');//road, 1.6m points
            //ReadLidar rl = new ReadLidar("../../../streetfront4.xyzi", ' ');

            //set up an array for each element
            double[] x, y, z, i;
            //set the values of those elements
            x = rl.NormalX;
            y = rl.NormalY;
            z = rl.NormalZ;
            i = rl.I;

            //set up all the values as floats
            float[] fx, fy, fz, fi;
            fx = new float[x.Length];
            fy = new float[y.Length];
            fz = new float[z.Length];
            fi = new float[i.Length];

            //the number of points in the file
            int elements = rl.elements;
            float rangeI, maxI;

            // convert all values to floats...why?
            for (int j = 0; j < x.Length; j++)
            {
                fx[j] = System.Convert.ToSingle(x[j]);
                fy[j] = System.Convert.ToSingle(y[j]);
                fz[j] = System.Convert.ToSingle(z[j]);
                fz[j] = System.Convert.ToSingle(i[j]);
            }

            //make a new array of vertex particles the size of elements
            m_sprites = new LidarPoint[rl.elements];
            //get the values range for intensity
            rangeI = System.Convert.ToSingle(rl.Range(i));
            //get the max value of intensity array
            maxI = System.Convert.ToSingle(rl.Range(i));

            //create a new lidar point for each read value
            for (int j = 0; j < elements; j++)
            {
                //create a new point
                m_sprites[j] = new LidarPoint();
                //set its position
                m_sprites[j].position = new Vector3(fx[j], fy[j], fz[j]);
                //set the color, based on intensity
                m_sprites[j].color = new Color((byte)fi[j], (byte)fi[j], (byte)fi[j]);
                //set Data, the position of the texture. why set it though?
                //every point has same texture base, unneeded info
                m_sprites[j].data = new Vector4(0, 0, 0, 0);
            }

            //set particle count. may not need it anymore though
            partCount = m_sprites.Length;
        }//end refreshParticles

        //returns a single lidar point and a specified index value
        public Vector3 getParticle (int i)
        {
            Vector3 v = m_sprites[i].position;
            return v;
        }

        //why is this overrided if it only calls to base?
        //does nothing.
        public override void Update(GameTime gameTime)
        {
            base.Update(gameTime);
        }

        //the big important method
        //draws the lidar points into the game.
        public override void Draw(GameTime gameTime)
        {
            bool PointSpriteEnable = myDevice.RenderState.PointSpriteEnable;
            float PointSize = myDevice.RenderState.PointSize;
            bool AlphaBlendEnable = myDevice.RenderState.AlphaBlendEnable;
            BlendFunction AlphaBlendOperation = myDevice.RenderState.AlphaBlendOperation;
            Blend SourceBlend = myDevice.RenderState.SourceBlend;
            Blend DestinationBlend = myDevice.RenderState.DestinationBlend;
            bool SeparateAlphaBlendEnabled = myDevice.RenderState.SeparateAlphaBlendEnabled;
            bool AlphaTestEnable = myDevice.RenderState.AlphaTestEnable;
            CompareFunction AlphaFunction = myDevice.RenderState.AlphaFunction;
            int ReferenceAlpha = myDevice.RenderState.ReferenceAlpha;
            bool DepthBufferWriteEnable = myDevice.RenderState.DepthBufferWriteEnable;

            if (myDevice.RenderState.PointSpriteEnable != true)
                myDevice.RenderState.PointSpriteEnable = true;
            if (myDevice.RenderState.PointSize != myPointSize)
                myDevice.RenderState.PointSize = myPointSize;
            if (myDevice.RenderState.AlphaBlendEnable != true)
                myDevice.RenderState.AlphaBlendEnable = true;
            if (myDevice.RenderState.AlphaBlendOperation != BlendFunction.Add)
                myDevice.RenderState.AlphaBlendOperation = BlendFunction.Add;
            if (myDevice.RenderState.SourceBlend != Blend.SourceAlpha)
                myDevice.RenderState.SourceBlend = Blend.SourceAlpha;
            if (myDevice.RenderState.DestinationBlend != Blend.One)
                myDevice.RenderState.DestinationBlend = Blend.One;
            if (myDevice.RenderState.SeparateAlphaBlendEnabled != false)
                myDevice.RenderState.SeparateAlphaBlendEnabled = false;
            if (myDevice.RenderState.AlphaTestEnable != true)
                myDevice.RenderState.AlphaTestEnable = true;
            if (myDevice.RenderState.AlphaFunction != CompareFunction.Greater)
                myDevice.RenderState.AlphaFunction = CompareFunction.Greater;
            if (myDevice.RenderState.ReferenceAlpha != 0)
                myDevice.RenderState.ReferenceAlpha = 0;
            if (myDevice.RenderState.DepthBufferWriteEnable != false)
                myDevice.RenderState.DepthBufferWriteEnable = false;

            //set up the VertexDeclaration as m_vDec specifies its shaders
            myDevice.VertexDeclaration = m_vDec;

            //create a matrix that controls where all the position and view angles are
            Matrix wvp = (
                Matrix.CreateScale(myScale) *
                Matrix.CreateFromQuaternion(myRotation) *
                Matrix.CreateTranslation(myPosition)) 
                * Camera.myView * Camera.myProjection;

            //set the shader to "worldViewProj" and pass it the wvp 
            effect.Parameters["WorldViewProj"].SetValue(wvp);

            //starts to apply the shader
            effect.Begin();
            //apply each of the shader techniques to the points
            //draw each of the points in the pointlist
            for (int ps = 0; ps < effect.CurrentTechnique.Passes.Count; ps++)
            {
                //apply the current technique
                effect.CurrentTechnique.Passes[ps].Begin();

                //draw the points with the current technique
                myDevice.DrawUserPrimitives<LidarPoint>(PrimitiveType.PointList,
                    m_sprites, 0,
                    m_sprites.Length);

                //end the current technique
                effect.CurrentTechnique.Passes[ps].End();
            }
            //stop the shader
            effect.End();

            if (myDevice.RenderState.PointSpriteEnable != PointSpriteEnable)
                myDevice.RenderState.PointSpriteEnable = PointSpriteEnable;
            if (myDevice.RenderState.PointSize != PointSize)
                myDevice.RenderState.PointSize = PointSize;
            if (myDevice.RenderState.AlphaBlendEnable != AlphaBlendEnable)
                myDevice.RenderState.AlphaBlendEnable = AlphaBlendEnable;
            if (myDevice.RenderState.AlphaBlendOperation != AlphaBlendOperation)
                myDevice.RenderState.AlphaBlendOperation = AlphaBlendOperation;
            if (myDevice.RenderState.SourceBlend != SourceBlend)
                myDevice.RenderState.SourceBlend = SourceBlend;
            if (myDevice.RenderState.DestinationBlend != DestinationBlend)
                myDevice.RenderState.DestinationBlend = DestinationBlend;
            if (myDevice.RenderState.SeparateAlphaBlendEnabled != SeparateAlphaBlendEnabled)
                myDevice.RenderState.SeparateAlphaBlendEnabled = SeparateAlphaBlendEnabled;
            if (myDevice.RenderState.AlphaTestEnable != AlphaTestEnable)
                myDevice.RenderState.AlphaTestEnable = AlphaTestEnable;
            if (myDevice.RenderState.AlphaFunction != AlphaFunction)
                myDevice.RenderState.AlphaFunction = AlphaFunction;
            if (myDevice.RenderState.ReferenceAlpha != ReferenceAlpha)
                myDevice.RenderState.ReferenceAlpha = ReferenceAlpha;
            if (myDevice.RenderState.DepthBufferWriteEnable != DepthBufferWriteEnable)
                myDevice.RenderState.DepthBufferWriteEnable = DepthBufferWriteEnable;

            //call to base to make sure all is done
            base.Draw(gameTime);

        }//end of draw method
    }//end of class
}//end of struct
